package org.xutils.db.sqlite;

import org.xutils.db.ex.DbException;
import org.xutils.db.log.Log;
import org.xutils.db.model.KeyValue;
import org.xutils.db.table.ColumnEntity;
import org.xutils.db.table.TableEntity;
import org.xutils.db.xDb;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Build "insert", "replace",，"update", "delete" and "create" sql.
 *
 * @Date: 2021/5/25 20:00
 * @Email: zzh_hz@126.com
 * @QQ: 1299234582
 * @Author: zzh
 * @Description: SQL语句构建
 */
public final class SqlInfoBuilder {

    private static final ConcurrentHashMap<TableEntity<?>, String> INSERT_SQL_CACHE = new ConcurrentHashMap<TableEntity<?>, String>();
    private static final ConcurrentHashMap<TableEntity<?>, String> REPLACE_SQL_CACHE = new ConcurrentHashMap<TableEntity<?>, String>();

    private SqlInfoBuilder() {
    }

    //*********************************************** insert sql ***********************************************

    public static SqlInfo buildInsertSqlInfo(TableEntity<?> table, Object entity) throws DbException {

        ColumnEntity id = table.getId();
        List<KeyValue> keyValueList = null;
        if (id.isAutoNumberId()) {
            keyValueList = entity2KeyValueList(table, entity);
        } else {
            keyValueList = entity2KeyValueList(table, entity, xDb.getInstance().generate());
        }

        if (keyValueList.isEmpty()) return null;

        SqlInfo result = new SqlInfo();
        String sql = INSERT_SQL_CACHE.get(table);
        if (sql == null) {
            StringBuilder builder = new StringBuilder();
            builder.append("INSERT INTO ");
            builder.append("\"").append(table.getName()).append("\"");
            builder.append(" (");
            for (KeyValue kv : keyValueList) {
                builder.append("\"").append(kv.key).append("\"").append(',');
            }
            builder.deleteCharAt(builder.length() - 1);
            builder.append(") VALUES (");

            int length = keyValueList.size();
            for (int i = 0; i < length; i++) {
                builder.append("?,");
            }
            builder.deleteCharAt(builder.length() - 1);
            builder.append(")");

            sql = builder.toString();
            result.setSql(sql);
            result.addBindArgs(keyValueList);
            INSERT_SQL_CACHE.put(table, sql);
        } else {
            result.setSql(sql);
            result.addBindArgs(keyValueList);
        }

        if (Log.getInstance().isDebugLogInsert()) {
            Log.getInstance().dx(new Exception(sql));
        }

        return result;
    }

    public static SqlInfo buildInsertSqlInfo(TableEntity<?> table, Object entity, String pkId) throws DbException {

        List<KeyValue> keyValueList = entity2KeyValueList(table, entity, pkId);
        if (keyValueList.size() == 0) return null;

        SqlInfo result = new SqlInfo();
        String sql = INSERT_SQL_CACHE.get(table);
        if (sql == null) {
            StringBuilder builder = new StringBuilder();
            builder.append("INSERT INTO ");
            builder.append("\"").append(table.getName()).append("\"");
            builder.append(" (");
            for (KeyValue kv : keyValueList) {
                builder.append("\"").append(kv.key).append("\"").append(',');
            }
            builder.deleteCharAt(builder.length() - 1);
            builder.append(") VALUES (");

            int length = keyValueList.size();
            for (int i = 0; i < length; i++) {
                builder.append("?,");
            }
            builder.deleteCharAt(builder.length() - 1);
            builder.append(")");

            sql = builder.toString();
            result.setSql(sql);
            result.addBindArgs(keyValueList);
            INSERT_SQL_CACHE.put(table, sql);
        } else {
            result.setSql(sql);
            result.addBindArgs(keyValueList);
        }

        if (Log.getInstance().isDebugLogInsert()) {
            Log.getInstance().dx(new Exception(sql));
        }

        return result;
    }

    //*********************************************** replace sql ***********************************************

    public static SqlInfo buildReplaceSqlInfo(TableEntity<?> table, Object entity) throws DbException {

        List<KeyValue> keyValueList = entity2KeyValueList(table, entity);
        if (keyValueList.size() == 0) return null;

        SqlInfo result = new SqlInfo();
        String sql = REPLACE_SQL_CACHE.get(table);
        if (sql == null) {
            StringBuilder builder = new StringBuilder();
            builder.append("REPLACE INTO ");
            builder.append("\"").append(table.getName()).append("\"");
            builder.append(" (");
            for (KeyValue kv : keyValueList) {
                builder.append("\"").append(kv.key).append("\"").append(',');
            }
            builder.deleteCharAt(builder.length() - 1);
            builder.append(") VALUES (");

            int length = keyValueList.size();
            for (int i = 0; i < length; i++) {
                builder.append("?,");
            }
            builder.deleteCharAt(builder.length() - 1);
            builder.append(")");

            sql = builder.toString();
            result.setSql(sql);
            result.addBindArgs(keyValueList);
            REPLACE_SQL_CACHE.put(table, sql);
        } else {
            result.setSql(sql);
            result.addBindArgs(keyValueList);
        }

        if (Log.getInstance().isDebugLogUpdate()) {
            Log.getInstance().dx(sql);
        }

        return result;
    }

    //*********************************************** delete sql ***********************************************

    public static SqlInfo buildDeleteSqlInfo(TableEntity<?> table, Object entity) throws DbException {
        SqlInfo result = new SqlInfo();

        ColumnEntity id = table.getId();
        Object idValue = id.getColumnValue(entity);

        if (idValue == null) {
            throw new DbException("this entity[" + table.getEntityType() + "]'s id value is null");
        }
        StringBuilder builder = new StringBuilder("DELETE FROM ");
        builder.append("\"").append(table.getName()).append("\"");
        builder.append(" WHERE ").append(WhereBuilder.b(id.getName(), "=", idValue));

        String sql = builder.toString();

        if (Log.getInstance().isDebugLogDelete()) {
            Log.getInstance().dx(sql);
        }

        result.setSql(sql);

        return result;
    }

    public static SqlInfo buildDeleteSqlInfoById(TableEntity<?> table, Object idValue) throws DbException {
        SqlInfo result = new SqlInfo();

        ColumnEntity id = table.getId();

        if (idValue == null) {
            throw new DbException("this entity[" + table.getEntityType() + "]'s id value is null");
        }
        StringBuilder builder = new StringBuilder("DELETE FROM ");
        builder.append("\"").append(table.getName()).append("\"");
        builder.append(" WHERE ").append(WhereBuilder.b(id.getName(), "=", idValue));

        String sql = builder.toString();

        if (Log.getInstance().isDebugLogDelete()) {
            Log.getInstance().dx(sql);
        }

        result.setSql(sql);


        return result;
    }

    public static SqlInfo buildDeleteSqlInfo(TableEntity<?> table, WhereBuilder whereBuilder) throws DbException {
        StringBuilder builder = new StringBuilder("DELETE FROM ");
        builder.append("\"").append(table.getName()).append("\"");

        if (whereBuilder != null && whereBuilder.getWhereItemSize() > 0) {
            builder.append(" WHERE ").append(whereBuilder.toString());
        }

        String sql = builder.toString();

        if (Log.getInstance().isDebugLogDelete()) {
            Log.getInstance().dx(sql);
        }
        return new SqlInfo(sql);
    }

    //*********************************************** update sql ***********************************************

    public static SqlInfo buildUpdateSqlInfo(TableEntity<?> table, Object entity, String... updateColumnNames) throws DbException {

        List<KeyValue> keyValueList = entity2KeyValueList(table, entity);
        if (keyValueList.size() == 0) return null;

        HashSet<String> updateColumnNameSet = null;
        if (updateColumnNames != null && updateColumnNames.length > 0) {
            updateColumnNameSet = new HashSet<String>(updateColumnNames.length);
            Collections.addAll(updateColumnNameSet, updateColumnNames);
        }

        ColumnEntity id = table.getId();
        Object idValue = id.getColumnValue(entity);

        if (idValue == null) {
            throw new DbException("this entity[" + table.getEntityType() + "]'s id value is null");
        }

        SqlInfo result = new SqlInfo();
        StringBuilder builder = new StringBuilder("UPDATE ");
        builder.append("\"").append(table.getName()).append("\"");
        builder.append(" SET ");
        for (KeyValue kv : keyValueList) {
            if ((updateColumnNameSet == null || updateColumnNameSet.contains(kv.key)) && !kv.autoGen) {
                builder.append("\"").append(kv.key).append("\"").append("=?,");
                result.addBindArg(kv);
            }
        }
        builder.deleteCharAt(builder.length() - 1);
        builder.append(" WHERE ").append(WhereBuilder.b(id.getName(), "=", idValue));

        String sql = builder.toString();
        if (Log.getInstance().isDebugLogUpdate()) {
            Log.getInstance().dx(new Exception(sql));
        }
        result.setSql(sql);
        return result;
    }

    public static SqlInfo buildUpdateSqlInfo(TableEntity<?> table, WhereBuilder whereBuilder, KeyValue... nameValuePairs) throws DbException {

        if (nameValuePairs == null || nameValuePairs.length == 0) return null;

        SqlInfo result = new SqlInfo();
        StringBuilder builder = new StringBuilder("UPDATE ");
        builder.append("\"").append(table.getName()).append("\"");
        builder.append(" SET ");
        for (KeyValue kv : nameValuePairs) {
            builder.append("\"").append(kv.key).append("\"").append("=?,");
            result.addBindArg(kv);
        }
        builder.deleteCharAt(builder.length() - 1);
        if (whereBuilder != null && whereBuilder.getWhereItemSize() > 0) {
            builder.append(" WHERE ").append(whereBuilder.toString());
        }

        String sql = builder.toString();
        if (Log.getInstance().isDebugLogUpdate()) {
            Log.getInstance().dx(new Exception(sql));
        }
        result.setSql(sql);
        return result;
    }

    //*********************************************** others ***********************************************

    public static SqlInfo buildCreateTableSqlInfo(TableEntity<?> table) throws DbException {
        ColumnEntity id = table.getId();

        StringBuilder builder = new StringBuilder();
        builder.append("CREATE TABLE IF NOT EXISTS ");
        builder.append("\"").append(table.getName()).append("\"");
        builder.append(" ( ");

        if (id.isAutoId()) {
            if (id.isAutoNumberId()) {
                builder.append("\"").append(id.getName()).append("\"").append(" INTEGER PRIMARY KEY AUTOINCREMENT, ");
            } else {
                builder.append("\"").append(id.getName()).append("\"").append(" TEXT NOT NULL PRIMARY KEY, ");
            }
        } else {
            builder.append("\"").append(id.getName()).append("\" ").append(id.getColumnDbType()).append(" PRIMARY KEY, ");
        }

        Collection<ColumnEntity> columns = table.getColumnMap().values();
        for (ColumnEntity column : columns) {
            if (column.isId()) continue;
            builder.append("\"").append(column.getName()).append("\"");
            builder.append(' ').append(column.getColumnDbType());
            builder.append(' ').append(column.getProperty());
            builder.append(',');
        }

        builder.deleteCharAt(builder.length() - 1);
        builder.append(" )");
        String sql = builder.toString();
        if (Log.getInstance().isDebugLogCreate()) {
            Log.getInstance().dx(new Exception(sql));

        }
        return new SqlInfo(sql);
    }

    public static List<KeyValue> entity2KeyValueList(TableEntity<?> table, Object entity) {
        return entity2KeyValueList(table, entity, null);
    }

    public static List<KeyValue> entity2KeyValueList(TableEntity<?> table, Object entity, String pkId) {
        Collection<ColumnEntity> columns = table.getColumnMap().values();
        List<KeyValue> keyValueList = new ArrayList<>(columns.size());
        StringBuilder sb = new StringBuilder();
        for (ColumnEntity column : columns) {
            KeyValue kv = column2KeyValue(entity, column, pkId);
            if (kv != null) {
                keyValueList.add(kv);
                sb.append(kv.key).append("=").append(kv.value).append(",");
            }
        }
        Log.getInstance().d(new DbException(sb.toString()));

        return keyValueList;
    }

    private static KeyValue column2KeyValue(Object entity, ColumnEntity column, String pkId) {
        if (column.isAutoNumberId()) {
            return null;
        } else if (column.isAutoGenId()) {
            return new KeyValue(column.getName(), pkId, true);
        }

        String key = column.getName();
        Object value = column.getFieldValue(entity);
        return new KeyValue(key, value);
    }
}
